package ca.cbc.gisdigester.publisher;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ca.cbc.giscommon.entity.Content;
import ca.cbc.giscommon.entity.Event;
import ca.cbc.giscommon.entity.EventSubcategory;
import ca.cbc.giscommon.entity.Story;
import ca.cbc.giscommon.util.CalendarUtil;
import ca.cbc.giscommon.util.GISConfig;
import ca.cbc.giscommon.util.StringUtil;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;

public enum Publisher {
	instance;
	private Logger logger = LoggerFactory.getLogger(Publisher.class);
	private Configuration cfg;
	private String bakeRoot;
	//private final static String EPT_STORY_TEMPLATE_NAME = "ept_story_listing";
	private final static String FEED_CONTENT_TEMPLATE_NAME = "feed_content_listing";
	private final static String EPT_STORY_AUTHOR_LISTING_TEMPLATE_NAME = "ept_story_author_listing";
	private final static String EVENT_TEMPLATE_NAME = "event_listing";
	
	private Publisher() {
        /* ------------------------------------------------------------------- */    
        /* You should do this ONLY ONCE in the whole application life-cycle:   */    
    
		bakeRoot = GISConfig.instance.getProperty("bake.dir");
		
        /* Create and adjust the configuration */
        try {
        	cfg = new Configuration();
			cfg.setDirectoryForTemplateLoading(new File(GISConfig.instance.getProperty("template.dir")));
			cfg.setObjectWrapper(new DefaultObjectWrapper());		
		} catch (IOException e) {
			logger.error("Cannot load template!", e);
		}
	}

	private void createDirs(String root, String path) throws Exception {
    	File rootDir = new File(root);
    	if(!rootDir.isDirectory())
    		throw new Exception("Root directory does not exist - " + rootDir);
    	
    	//get absolute path
    	if(!rootDir.isAbsolute()) {
    		path = rootDir.getAbsolutePath().replace("\\", "/") + "/" + path;
    	} else {
    		path = rootDir + "/" + path;
    	}
    
    	File currentDir = new File(path.substring(0, path.lastIndexOf("/")));
    	
    	if(currentDir.exists() == false) {
    		currentDir.mkdirs();
    		logger.info("Directory created: {}", currentDir);
    	}
    }
	
	/**
	 * 
	 * @param stories -- must in the same category
	 * @param path -- relative file path
	 * @throws Exception 
	 */
	@SuppressWarnings("rawtypes")
	private void publish(List list, String dirName, String templateName, String outputFilename) throws Exception {
        /* ------------------------------------------------------------------- */    
        /* You usually do these for many times in the application life-cycle:  */    
		String path = dirName + "/" + outputFilename + ".html";
		
        /* Get or create a template */
        Template temp = cfg.getTemplate(templateName + ".ftl");

        /* Create a data-model */
        Map<String, List> root = new HashMap<String, List>();
        root.put("root", list);

        /* Merge data-model with template */
        this.createDirs(bakeRoot, path);
        Writer out = new BufferedWriter(new FileWriter(bakeRoot + "/" + path));
        temp.process(root, out);
        out.flush();	
        logger.info("***{} stories/feeds published to {}.", list.size(), bakeRoot + "/" + path);
	}
	

	public void publishEptStoryByAuthor(List<Story> stories) throws Exception {
		Map<String, List<Story>> storyMap = new HashMap<String, List<Story>>();
		for(Story story : stories) {
			String keyName = story.getAuthorName();
			if(!StringUtil.instance.isNullOrEmpty(keyName)) {
				String key = StringUtil.instance.trimAndNormalize(keyName);
				if(!storyMap.containsKey(key)) {
					storyMap.put(key, new ArrayList<Story>());
				}
				storyMap.get(key).add(story);
			}
		}

		for(String key : storyMap.keySet()) {
			logger.info("***Publishing etp stories with author {}.", key);
			this.publish(storyMap.get(key), "authors", EPT_STORY_AUTHOR_LISTING_TEMPLATE_NAME, key);
		}	
	}
	
	/**
	 * 
	 * @param events
	 * @param category -- event can only be things to do category
	 * @throws Exception
	 */
	public void publishEventByCategory(List<Event> events) throws Exception {
		Map<String, List<Event>> map = new HashMap<String, List<Event>>();
		//put event group by subcategory
		for(Event event : events) {
			List<EventSubcategory> subCategories = event.getEventSubcategories();
			for(EventSubcategory es : subCategories) {
				String key = StringUtil.instance.trimAndNormalize(es.getSubcategory());
				if(!map.containsKey(key)) {
					map.put(key, new ArrayList<Event>());
				}
				map.get(key).add(event);
			}
		}
		
		//add all events
		map.put("all", events);
		
		//all events should be "things to do" category
		String categoryName = StringUtil.instance.trimAndNormalize(events.get(0).getSource().getCategory().getName());
		
		for(String key : map.keySet()) {
			logger.info("***Publishing event with subcategory {}.", key);
			
			List<Event> today = filterEventsByTime(map.get(key), CalendarUtil.instance.getStartTimeOfToday(), CalendarUtil.instance.getEndTimeOfToday());
			List<Event> thisWeek = filterEventsByTime(map.get(key), CalendarUtil.instance.getStartTimeOfToday(), CalendarUtil.instance.getLastDayOfCurrentWeek());
			List<Event> thisMonth = filterEventsByTime(map.get(key), CalendarUtil.instance.getStartTimeOfToday(), CalendarUtil.instance.getLastDayOfCurrentMonth());
			List<Event> nextMonth = filterEventsByTime(map.get(key), CalendarUtil.instance.getFirstDayOfNextMonth(), CalendarUtil.instance.getLastDayOfNextMonth());
			
			this.publish(today, EVENT_TEMPLATE_NAME + "/" + categoryName + "/" + key + "/today", EVENT_TEMPLATE_NAME, EVENT_TEMPLATE_NAME);
			this.publish(thisWeek, EVENT_TEMPLATE_NAME + "/" + categoryName + "/" + key + "/this_week", EVENT_TEMPLATE_NAME, EVENT_TEMPLATE_NAME);
			this.publish(thisMonth, EVENT_TEMPLATE_NAME + "/" + categoryName + "/" + key + "/this_month", EVENT_TEMPLATE_NAME, EVENT_TEMPLATE_NAME);
			this.publish(nextMonth, EVENT_TEMPLATE_NAME + "/" + categoryName + "/" + key + "/next_month", EVENT_TEMPLATE_NAME, EVENT_TEMPLATE_NAME);
		}
	}
	
	private List<Event> filterEventsByTime(List<Event> events, long start, long end) {
		List<Event> list = new ArrayList<Event>();
		for(Event event : events) {
			long eventStart = event.getStart() == null ? 0 : event.getStart().getTime();
			long eventEnd = event.getEnd() == null ? 0 : event.getEnd().getTime();
			if((eventStart >= start && eventStart <= end) ||
				eventEnd >= start && eventEnd <= end || 
				eventStart < start && eventEnd > end) {
				list.add(event);
			}
		}
		return list;
	}
	
    public void publishFeedContentByCategory(List<Content> contents) throws Exception {
		Map<String, List<Content>> categoryContentsMap = new HashMap<String, List<Content>>();
		for(Content content : contents) {
			String key = StringUtil.instance.trimAndNormalize(content.getSource().getCategory().getName());
			if(!categoryContentsMap.containsKey(key)) {
				categoryContentsMap.put(key, new ArrayList<Content>());
			}
			categoryContentsMap.get(key).add(content);
		}
		
		for(String key : categoryContentsMap.keySet()) {
			logger.info("***Publishing feed contents with cateory {}.", key);
			this.publish(categoryContentsMap.get(key), key, FEED_CONTENT_TEMPLATE_NAME, FEED_CONTENT_TEMPLATE_NAME);
		}
	}
}
